﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using gudusoft.gsqlparser;
using gudusoft.gsqlparser.Units;
using System.IO;

namespace VariableAnalyze
{
    public class TFunction
    {
        public string functionName;
        public string returnType;
        public TParameter[] parameters;
        public TVariable[] variables;
        public string lineNumber;
    }

    public class TParameter
    {
        public string paramName;
        public string dataType;
        public string lineNumber;
    }


    public class TVariable
    {
        public string varName;
        public string dataType;
        public string lineNumber;
    }


    public class variableAnalyze
    {
        private StringBuilder buffer = new StringBuilder();
        private Hashtable functionMap = new Hashtable();

        public string[] getFunctionNames()
        {
            return (string[])functionMap.Keys;
        }

        public TFunction getFunction(string functionName)
        {
            return (TFunction)functionMap[functionName];
        }

        public string AnalysisResult
        {
            get { return buffer.ToString(); }
        }


        static void Main(string[] args)
        {
            if (args.Length == 0)
            {
                Console.WriteLine("Usage: {0} <sql script file path> <output file path>", Environment.GetCommandLineArgs()[0]);
                Console.WriteLine("sql script file path: The sql file will be analyzed.");
                Console.WriteLine("output file path: Option, write the analysis result to the specified file.");
                //Console.Read();
                return;
            }

            string outputFile = null;
            StreamWriter writer = null;
            if (args.Length > 1)
            {
                outputFile = args[1];
            }

            if (outputFile != null)
            {
                try
                {
                    writer = new StreamWriter(outputFile);
                    Console.SetOut(writer);
                }
                finally { }
            }
            variableAnalyze analysis = new variableAnalyze(new FileInfo(args[0]), TDbVendor.DbVOracle);
            Console.Write(analysis.AnalysisResult);
            //if (args.Length <= 1)
            //{
            //    Console.Read();
            //}
            //else
            {
                if (writer != null)
                {
                    writer.Close();
                }
            }

        } //main

        public variableAnalyze(string sql, TDbVendor dbVendor)
        {
            TGSqlParser sqlparser = new TGSqlParser(dbVendor);
            sqlparser.SqlText.Text = sql;
            analyzeSQL(sqlparser);
        }

        public variableAnalyze(FileInfo file, TDbVendor dbVendor)
        {
            TGSqlParser sqlparser = new TGSqlParser(dbVendor);
            sqlparser.Sqlfilename = file.ToString();
            analyzeSQL(sqlparser);
        }

        private void analyzeSQL(TGSqlParser sqlparser)
        {
            int ret = sqlparser.Parse();

            if (ret != 0)
            {
                buffer.AppendLine(sqlparser.ErrorMessages);
                return;
            }

            analyzeSql(sqlparser);

            foreach (string key in functionMap.Keys)
            {
                TFunction function = (TFunction)functionMap[key];
                buffer.AppendLine(System.Environment.NewLine + "Function:" + function.functionName);
                buffer.AppendLine("Return Data Type:" + function.returnType);
                buffer.AppendLine("Line Number:" + function.lineNumber);

                if (function.parameters != null)
                {
                    foreach (TParameter param in function.parameters)
                    {
                        buffer.AppendLine(System.Environment.NewLine + "Parameter:" + param.paramName);
                        buffer.AppendLine("Data Type:" + param.dataType);
                        buffer.AppendLine("Line Number:" + param.lineNumber);
                    }
                }

                if (function.variables != null)
                {
                    foreach (TVariable variable in function.variables)
                    {
                        buffer.AppendLine(System.Environment.NewLine + "Variable:" + variable.varName);
                        buffer.AppendLine("Data Type:" + variable.dataType);
                        buffer.AppendLine("Line Number:" + variable.lineNumber);
                    }
                }
            }
        }

        private void analyzeSql(TGSqlParser sqlparser)
        {
            for (int i = 0; i < sqlparser.SqlStatements.Count(); i++)
            {
                if (sqlparser.SqlStatements[i] is TPlsqlStatement)
                {
                    TPlsqlStatement stmt = (TPlsqlStatement)sqlparser.SqlStatements[i];
                    if (stmt.SqlStatementType == TSqlStatementType.sstplsql_createpackage)
                    {
                        TLzPlsql_Package pkg = (TLzPlsql_Package)stmt.Root;
                        if (pkg._definitions != null)
                        {
                            for (int j = 0; j < pkg._definitions.Count(); j++)
                            {
                                if (pkg._definitions[j] is TLzPlsql_SubProgram)
                                {
                                    TLzPlsql_SubProgram procedure = (TLzPlsql_SubProgram)pkg._definitions[j];
                                    if (procedure._isFunction == true)
                                    {
                                        TFunction function = new TFunction();
                                        string functionName = procedure._procedure_name.AsText;
                                        string returnType = procedure._plsql_datatype.AsText;
                                        function.functionName = functionName;
                                        function.returnType = returnType;
                                        int index = procedure._procedure_name.ObjectNameToken.posinlist;
                                        int lineNumber = getLineNumber(stmt, index);
                                        function.lineNumber = lineNumber.ToString();
                                        functionMap.Add(functionName, function);

                                        if (procedure._parameters != null && procedure._parameters.Count() > 0)
                                        {
                                            List<TParameter> paramList = new List<TParameter>();
                                            for (int k = 0; k < procedure._parameters.Count(); k++)
                                            {
                                                if (procedure._parameters[k] is TLzPlsql_ParameterDecl)
                                                {
                                                    TLzPlsql_ParameterDecl param = (TLzPlsql_ParameterDecl)procedure._parameters[k];
                                                    string paramName = param._parameter_name.AsText;
                                                    string dataType = param._plsql_datatype.AsText;
                                                    TParameter parameter = new TParameter();
                                                    parameter.paramName = paramName;
                                                    parameter.dataType = dataType;
                                                    index = param._parameter_name.posinlist;
                                                    lineNumber = getLineNumber(stmt, index);
                                                    parameter.lineNumber = lineNumber.ToString();
                                                    paramList.Add(parameter);
                                                }
                                            }
                                            function.parameters = paramList.ToArray();
                                        }
                                        if (procedure._decl_stmts != null && procedure._decl_stmts.Count() > 0)
                                        {
                                            List<TVariable> variableList = new List<TVariable>();
                                            for (int k = 0; k < procedure._decl_stmts.Count(); k++)
                                            {
                                                if (procedure._decl_stmts[k] is TLzPlsql_VarDeclStmt)
                                                {
                                                    TLzPlsql_VarDeclStmt var = (TLzPlsql_VarDeclStmt)procedure._decl_stmts[k];
                                                    string varName = var._varname.AsText;
                                                    string dataType = var._typename.AsText;
                                                    TVariable variable = new TVariable();
                                                    variable.varName = varName;
                                                    variable.dataType = dataType;
                                                    index = var._varname.posinlist;
                                                    lineNumber = getLineNumber(stmt, index);
                                                    variable.lineNumber = lineNumber.ToString();
                                                    variableList.Add(variable);
                                                }
                                            }
                                            function.variables = variableList.ToArray();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private static int getLineNumber(TPlsqlStatement stmt, int index)
        {
            int line = 1;
            for (int x = 0; x < index; x++)
            {
                string text = stmt.SourceTokenList[x].AsText;
                for (int y = 0; y < text.Length; y++)
                {
                    if (text[y] == '\n')
                        line++;
                }
            }
            return line;
        }
    }
}
